package main

import (
	"errors"
	"fmt"
	"golang.org/x/net/context"
	"golang.org/x/sync/singleflight"
	_ "net/http/pprof"
	"sync"
	"sync/atomic"
	"time"
)

var count int32

func TestSingleFlight() {
	var (
		wg  sync.WaitGroup
		now = time.Now()
		n   = 1000
		sg  = &singleflight.Group{}
	)

	atomic.StoreInt32(&count, 0)

	//循环查询1000次
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() {
			res, _ := getArticle(1)
			if res != "article: 1" {
				panic("err")
			}
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Printf("同时发起 %d 次请求，耗时: %s", count, time.Since(now))
	fmt.Println("----------------------")
	atomic.StoreInt32(&count, 0)
	now = time.Now()
	for i := 0; i < n; i++ {
		wg.Add(1)

		go func() {
			res, _ := singleFlightGetArticle(sg, 1)
			//res, _ := getArticle(1)
			if res != "article: 1" {
				panic("err")
			}
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Printf("single Flight do同时发起 %d 次请求，耗时: %s", count, time.Since(now))
	fmt.Println("------------")
	//DoChan

	atomic.StoreInt32(&count, 0)
	now = time.Now()
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() {
			res, _ := singleFlightGetArticleChannel(sg, 1)
			//res, _ := getArticle(1)
			if res != "article: 1" {
				panic("err")
			}
			wg.Done()
		}()
	}

	wg.Wait()
	fmt.Printf("singleFlight DoChan同时发起%d次请求，耗时: %s", count, time.Since(now))

}

func singleFlightGetArticle(sg *singleflight.Group, id int) (string, error) {
	v, err, _ := sg.Do(fmt.Sprintf("%d", id), func() (interface{}, error) {
		return getArticle(id)
	})
	return v.(string), err
}

/*
通过do channel， context 超时处理
*/
func singleFlightGetArticleChannel(sg *singleflight.Group, id int) (string, error) {
	channel := sg.DoChan(fmt.Sprintf("%d", id), func() (interface{}, error) {
		return getArticle(id)
	})

	ctx, _ := context.WithTimeout(context.Background(), 4*time.Second)
	for { // 选择 context + select 超时控制
		select {
		case <-ctx.Done():
			fmt.Println("singleFlightGetArticleChannel time out")
			return "", errors.New("ctx-timeout") // 根据业务逻辑选择上抛 error
		case data, _ := <-channel:
			return data.Val.(string), nil
		default:
			fmt.Println("singleFlightGetArticleChannel default")
		}
	}
}

/*
模拟获取文章详情
*/
func getArticle(id int) (article string, err error) {
	atomic.AddInt32(&count, 1)
	//time.Sleep(time.Duration(count) * time.Millisecond)
	fmt.Printf("getArticle run times %d", count)
	return fmt.Sprintf("article: %d", id), nil
}
